// Copyright (c) 2022.
// Written by Li Yanxiao. All rights reserved

#include "broken_line.h"
#include <iostream>

using std::cout;
using std::endl;

std::random_device RD;
std::ranlux48 RANDOM_ENGINE(RD());


unsigned int *RandomDot(unsigned int *dst, unsigned int *minVal, unsigned int *maxVal) {
    std::uniform_int_distribution<unsigned int> distrib_x(minVal[0], maxVal[0]);
    std::uniform_int_distribution<unsigned int> distrib_y(minVal[1], maxVal[1]);
    dst[0] = distrib_x(RANDOM_ENGINE);
    dst[1] = distrib_y(RANDOM_ENGINE);
    return dst;
}

Eigen::Vector2i *RandomDot(Eigen::Vector2i *dst, const Eigen::Vector2i& minVal, const Eigen::Vector2i& maxVal) {
    assert(dst != nullptr); // 空指针检测
    std::uniform_int_distribution<unsigned int> distrib_x(minVal.x(), maxVal.x());
    std::uniform_int_distribution<unsigned int> distrib_y(minVal.y(), maxVal.y());
    *dst = Eigen::Vector2i(distrib_x(RANDOM_ENGINE), distrib_y(RANDOM_ENGINE));
    return dst;
}

Line GenerateFirstLine(unsigned width, unsigned height, unsigned distance) {
    unsigned int p_start[2], p_end[2];
    p_start[0] = distance;
    p_start[1] = distance;
    p_end[0] = width - distance;
    p_end[1] = height - distance;

    unsigned int result_end[2];
    unsigned int result_start[2];
    // 为了防止生成一个长度为0的线段，用do…while限制一下
    do {
        RandomDot(result_start, p_start, p_end);
        RandomDot(result_end, p_start, p_end);
    }
    // 这里的意思是当生成的两个点横纵坐标都相等就再来一次
    while (result_start[0] == result_end[0] && result_end[1] == result_start[1]);

    Eigen::Vector2i x(result_start[0], result_start[1]);
    Eigen::Vector2i y(result_end[0], result_end[1]);
    return {x,y};
}

Line
GenerateSecondLine(const Line &first_line, unsigned int width, unsigned int height,
                   unsigned int distance) {
    // 先初始化一个Eigen::Vector2i，用来存放生成的结束点的位置
    Eigen::Vector2i end_point(0,0);
    // 由于我自己写的RandomDot方法需要一个指向Vector2i的指针，在这里定义一个
    Eigen::Vector2i *p_end_point = &end_point;
    Eigen::Vector2i minVal(distance, distance);
    Eigen::Vector2i maxVal(width-distance, height - distance);
    do{
        RandomDot(p_end_point, minVal, maxVal);
    }
    // 这里的意思是：当这条线对应向量点乘第一条线向量大于0（即夹角为锐角时），重新生成
    while((first_line.getEndPoint() - end_point).dot(first_line.vector()) > 0);

    return {first_line.getEndPoint(), end_point};
}

Line
GenerateThirdLine(const Line& first_line, const Line& second_line, unsigned int width,
                  unsigned int height, unsigned int distance) {
    Eigen::Vector2i end_point(0,0);
    Eigen::Vector2i *p_end_point = &end_point;
    Eigen::Vector2i minVal(distance, distance);
    Eigen::Vector2i maxVal(width-distance, height - distance);
    do{
        RandomDot(p_end_point, minVal, maxVal);
    }
    // 这里的限制是：
    // 当这条线向量点乘第二条线向量小于0（即夹角为钝角时）重新生成
    // 当第一条线与第三条线有交点时也重新生成
    while(((second_line.getEndPoint() - end_point).dot(second_line.getEndPoint() - second_line.getStartPoint()) < 0)
    or collide(first_line, Line(second_line.getEndPoint(), end_point)));

    return {second_line.getEndPoint(), end_point};
}

bool collide(const Line& first_line, const Line& second_line, int width, int height) {
    /*
     * 这里利用了公式
     * x = (b1c2 - b2c1) / (b2 - b1)
     * y = (c1 - c2) / (b2 - b1)
     * 利用两条线的b与c直接求出了它们交点的坐标
     */
    int x = floor(round((first_line.getB() * second_line.getC() - second_line.getB() * first_line.getC())
            / (second_line.getB() - first_line.getB())));
    int y = floor(round((first_line.getC() - second_line.getC()) / (second_line.getB() - first_line.getB())));

    // 创建一个Vector2i对象，内容就是交点，方便接下来对向量点乘的计算
    Eigen::Vector2i mix_point(x,y);

    // 需要用第一条线段末尾指向头部的向量点乘第一条线段末尾指向交点的向量，可以得出它们的夹角大小，需要是钝角才行
    Eigen::Vector2i negative_vector(-first_line.vector().x(), -first_line.vector().y());
    if (negative_vector.dot(mix_point - first_line.getEndPoint()) < 0
        // 这里是第二条线段头部指向末尾的向量点乘第二条线末尾指向交点的向量，可以得出它们的夹角大小，需要是钝角才行
        and second_line.vector().dot(mix_point - second_line.getStartPoint()) < 0){
        return false;
    }
    // 这个交点不在图像内部是直接通过的（相当于没交点）
    if (x < 0 or x > width or y < 0 or y > height){
        return false;
    }


    return true;
}

Line *GenerateBrokenLine(Line *dst, unsigned int width, unsigned int height, unsigned int distance) {
    assert(dst != nullptr); // 防止dst是个空指针
    dst[0] = GenerateFirstLine(width, height, distance);
    dst[1] = GenerateSecondLine(dst[0], width, height, distance);
    dst[2] = GenerateThirdLine(dst[0], dst[1], width, height, distance);

    return dst;
}
